﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using KamLib;

namespace KamLib
{
    public interface IUilMethod
    {
        void Invoke(object sender, UilMethodArgs e);
        UilAction AsAction(UilController controller, IUilViewPart viewPart);
    }


    public class UilMethodAttribute : UilIDAttribute
    {
        public UilMethodAttribute() : base(null) { }
        public UilMethodAttribute(string id) : base(id) { }
    }




    public class UilMethodArgs
    {
        public UilMethodArgs() { }

        public UilMethodArgs(UilController controller, IDictionary<string, object> values = null)
        {
            Context = controller.Context;
            Key = controller.Key;
            Controller = controller;
            Values = values;
        }

        public UilMethodArgs(UilContext context, UilKey key, IDictionary<string, object> values = null)
        {
            Context = context;
            Key = key;
            Controller = context.GetController(key);
            Values = values;
        }


        public UilContext Context;
        public UilController Controller;
        public UilKey Key;
        public string MethodAlias;
        public IUilMethod Method;
        public object Result;
        public IDictionary<string, object> Values;

        #region Tools

        public void ShowInfo(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddInfo(Message, Title, Pinned, Width, Height);
        }

        public void ShowError(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddError(Message, Title, Pinned, Width, Height);
        }

        public void ShowWarning(string Message, string Title = null, bool? Pinned = null, int? Width = null, int? Height = null)
        {
            Context.Notifications.AddWarning(Message, Title, Pinned, Width, Height);
        }

        #endregion
    }


    public static class UilMethod
    {

        public static UilMethodArgs Invoke(UilController controller, string name, IDictionary<string, object> values)
        {
            var e = new UilMethodArgs(controller, values) { MethodAlias = name };
            var method = e.Controller.FindMethod(e) ?? UilRouteTable.HandlerMethods.Try(name);
            if (method == null)
                throw new Exception("UilMethod.Invoke()\r\nCan't find method '" + name + "' of controller '" + e.Key.CTID + "'.");
            e.Method = method;
            method.Invoke(controller, e);
            return e;
        }

        public static UilMethodArgs Invoke(UilContext context, UilKey key, string methodAlias, IDictionary<string, object> values)
        {
            return Invoke(context.GetController(key), methodAlias, values);
        }

    }

    public class UilRefMethod : IUilMethod
    {
        public readonly string MID;
        public readonly UilMethodAttribute MethodAttribute;
        public readonly UilActionAttribute ActionAttribute;
        public readonly bool IsStatic;
        public readonly MethodInfo Method;
        public readonly Type SenderType;


        public UilRefMethod(string mid, UilMethodAttribute methodAttribute, Type senderType, MethodInfo method, bool isStatic = false)
        {
            if (senderType == null)
                throw new ArgumentNullException("senderType");
            if (method == null)
                throw new ArgumentNullException("method");

            MID = mid;
            MethodAttribute = methodAttribute;
            ActionAttribute = methodAttribute as UilActionAttribute;
            SenderType = senderType;
            Method = method;
            IsStatic = isStatic;
        }

        #region IUilMethod Members

        public void Invoke(object sender, UilMethodArgs e)
        {
            var prms = Method.GetParameters();
            var margs = new object[prms.Length];
            var argsType = typeof(UilMethodArgs);

            for (var i = 0; i < prms.Length; ++i)
            {
                var prm = prms[i];
                margs[i] = argsType.IsAssignableFrom(prm.ParameterType)
                    ? e
                    : e.Values.Try(prm.Name).To(prm.ParameterType);
            }

            e.Result = Method.Invoke(IsStatic ? null : sender, margs);
        }

        public UilAction AsAction(UilController controller, IUilViewPart viewPart)
        {
            return ActionAttribute != null ? new UilAction(controller, viewPart, MID, ActionAttribute) : null;
        }

        #endregion

        /// <summary>
        ///     Создает список методов класса type (с аттрибутом UilMethodAttribute)
        /// </summary>
        /// <param name="type"> где искать методы </param>
        /// <param name="dest"> Если dest == null, то создаёт и возвращает новый </param>
        /// <param name="useStatic"> Если true - искать статические методы. Если false - нестатические. </param>
        /// <param name="useFullID"> Если true - добавлять к ID метода ID класса, где он объявлен </param>
        /// <returns> </returns>
        public static IDictionary<string, IUilMethod> GetMethods(
            Type type, IDictionary<string, IUilMethod> dest = null,
            bool useStatic = false, bool useFullID = false
            )
        {
            if (dest == null) dest = new SortedDictionary<string, IUilMethod>();

            var methods = useStatic
                ? type.GetMethods(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static)
                : type.GetMethods();

            var fid = useFullID ? UilIDAttribute.GetID(type) + "." : null;

            foreach (var mi in methods)
            {
                var attr = mi.GetAttribute<UilMethodAttribute>();
                if (attr != null)
                {
                    var mid = fid + (attr.ID ?? mi.Name);
                    dest[mid] = new UilRefMethod(mid, attr, type, mi);
                }
            }

            return dest;
        }
    }
}